/* Copyright (c) 2008, 2009, 2010, 2011, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{   
    abstract partial class Assembler : IByteOperations
    {
        public LockedInt next_object_id = 0;

        public bool ProduceIrDump = false;
        private string _irdump = "";
        public string IrDump { get { return _irdump; } }

        public int maxCoercionCountPerInstruction = 10;

        public class AssemblerSecurity
        {
            public bool AllowConvORefToUI4 = false;

            public static AssemblerSecurity CorlibSecurity
            {
                get
                {
                    return new AssemblerSecurity
                    {
                        AllowConvORefToUI4 = true
                    };
                }
            }
        }

        public class AssemblerState
        {
            public int next_variable = 1;
            public int next_block = 1;
            public IRegisterAllocator reg_alloc;
            public AssemblerSecurity security = new AssemblerSecurity();
            public List<hardware_location> local_vars = new List<hardware_location>();
            public List<hardware_location> local_args = new List<hardware_location>();
            public int stack_space_used = 0;
        }

        public interface IrDumpFeedback
        {
            void IrDump(string line);
        }
        public IrDumpFeedback IrFeedback = null;
        public IrDumpFeedback OptimizedIrFeedback = null;

        public const string PureVirtualSig = "__cxa_pure_virtual";

        public struct MethodToCompile : IEquatable<MethodToCompile>
        {
            public Metadata.MethodDefRow meth; public Signature.BaseMethod msig;
            public Metadata.TypeDefRow type; public Signature.BaseOrComplexType tsig;
            public MethodToCompile(Assembler ass) { _ass = ass; meth = null; msig = null; type = null; tsig = null; }
            Assembler _ass;
            #region IEquatable<MethodToCompile> Members

            public bool Equals(MethodToCompile other)
            {
                throw new NotSupportedException();
                /*if (this.meth != other.meth)
                    return false;
                if (this.msig != other.msig)
                    return false;
                if (this.type != other.type)
                    return false;
                if (this.tsig != other.tsig)
                    return false;
                return true;*/
            }

            #endregion

            public override string ToString()
            {
                return Mangler.Mangle(this, _ass);
            }

            public Assembler.TypeToCompile GetTTC(Assembler ass) { return new Assembler.TypeToCompile { type = type, tsig = new Signature.Param(tsig, ass) }; }
        }

        public struct FieldToCompile
        {
            public Metadata.FieldRow field; public Signature.Param fsig;
            public Metadata.TypeDefRow definedin_type; public Signature.Param definedin_tsig;
            public Metadata.TypeDefRow memberof_type; public Signature.Param memberof_tsig;

            public static FieldToCompile GetFTC(Assembler.TypeToCompile ttc, string mangled_name, Assembler ass)
            {
                for (Metadata.TableIndex ti = ttc.type.FieldList; ti < Metadata.GetLastField(ttc.type.m, ttc.type); ti++)
                {
                    Metadata.FieldRow fr = ti.Value as Metadata.FieldRow;
                    Signature.Param fsig = Signature.ResolveGenericParam(Signature.ParseFieldSig(fr.m, fr.Signature).AsParam(ass), ttc.tsig.Type, null, ass);
                    FieldToCompile ftc = new FieldToCompile { fsig = fsig, field = fr, definedin_tsig = ttc.tsig, definedin_type = ttc.type };

                    if (mangled_name.StartsWith("_"))
                    {
                        if (Mangler.FieldOnlyMangle(fr, new Signature.Field(fsig), ass) == mangled_name)
                            return ftc;
                    }
                    else
                    {
                        if (Signature.GetString(ftc, ass) == mangled_name)
                            return ftc;
                    }
                }
                throw new KeyNotFoundException();
            }

            public TypeToCompile DefinedIn { get { return new TypeToCompile { tsig = definedin_tsig, type = definedin_type }; } }
            public TypeToCompile MemberOf { get { return new TypeToCompile { tsig = memberof_tsig, type = memberof_type }; } }
        }

        public struct TypeToCompile : IEquatable<TypeToCompile>
        {
            public Metadata.TypeDefRow type; public Signature.Param tsig;
            Assembler _ass;
            public TypeToCompile(Assembler ass) { _ass = ass; type = null; tsig = null; }

            #region IEquatable<MethodToCompile> Members

            public bool Equals(TypeToCompile other)
            {
                throw new NotSupportedException();
                /*if (this.type != other.type)
                    return false;
                if (this.tsig != other.tsig)
                    return false;
                return true;*/
            }

            public TypeToCompile(Signature.Param p, Assembler ass) : this(ass)
            { type = Metadata.GetTypeDef(p.Type, ass); tsig = p; }

            public TypeToCompile(Metadata.TypeDefRow _type, Signature.Param _tsig, Assembler ass) : this(ass)
            { type = _type; tsig = _tsig; }

            public TypeToCompile(Metadata.TypeDefRow _type, Assembler ass) : this(ass)
            {
                type = _type;
                tsig = new Signature.Param(new Token(_type), ass);
            }

            public TypeToCompile(Token tok, Assembler ass) : this(ass)
            {
                type = Metadata.GetTypeDef(tok, ass);
                tsig = new Signature.Param(tok, ass);
            }

            public override string ToString()
            {
                return Mangler.Mangle(this, _ass);
            }

            #endregion
        }

        protected interface IStackAllocator
        {
            hardware_stackloc GetNextStackLoc(var var_id, int size);
            int GetStackSize();
        }

        public interface IRegisterAllocator
        {
            hardware_location GetRegister(hloc_constraint constraint, var var_id);
            void FreeRegister(hardware_location hloc);
            void FreeCoercions();
            hardware_location GetTemporaryRegister();

            LocalVarArgHardwareAllocator GetLocalArgAllocator();
            LocalVarArgHardwareAllocator GetLocalVarAllocator();
            LocalVarArgHardwareAllocator GetLogicalVarAllocator();

            IEnumerable<hardware_location> AllOfType(System.Type type);

            IRegisterAllocator Clone();
        }

        public abstract class HardwareStackAllocator : IStackAllocator
        {
            protected abstract int GetStackAlign();
            protected abstract bool AllocatesDownwards();

            protected int cur_stack_loc = 0;
            protected Dictionary<int, var> stack = new Dictionary<int,var>();

            public virtual int GetStackSize() { return cur_stack_loc; }

            public hardware_stackloc GetNextStackLoc(var var_id, int size)
            {
                // determine the actual size of the stack item (make a multiple of GetStackAlign())
                int s_align = GetStackAlign();
                int s_loc;

                if ((size % s_align) != 0)
                    size = ((size / s_align) + 1) * s_align;

                if (AllocatesDownwards())
                {
                    s_loc = cur_stack_loc - size;
                    cur_stack_loc = s_loc;
                }
                else
                {
                    s_loc = cur_stack_loc;
                    cur_stack_loc += size;
                }

                stack.Add(s_loc, var_id);

                return new hardware_stackloc { loc = s_loc };
            }

            public HardwareStackAllocator Clone()
            {
                HardwareStackAllocator clone = this.GetType().GetConstructor(new Type[] { }).Invoke(new object[] { }) as HardwareStackAllocator;
                clone.cur_stack_loc = cur_stack_loc;
                clone.stack = new Dictionary<int, var>(stack);
                return clone;
            }
        }

        public abstract class LocalVarArgHardwareAllocator
        {
            protected Dictionary<int, hardware_location> lvars = new Dictionary<int, hardware_location>();
            protected Dictionary<int, hardware_location> largs = new Dictionary<int, hardware_location>();
            protected Dictionary<int, hardware_location> logs = new Dictionary<int, hardware_location>();

            protected abstract hardware_location AllocateHardwareLocation(var v, int size);

            protected hardware_location GetHardwareLocation(int n, int size, var v, Dictionary<int, hardware_location> dict)
            {
                if (dict.ContainsKey(n))
                    return dict[n];

                if (size <= 0)
                    throw new Exception("Need to allocate hardware location but no size specified");

                hardware_location ret = AllocateHardwareLocation(v, size);
                if (ret == null)
                    throw new Exception("Unable to allocate hardware location");
                dict.Add(n, ret);
                return ret;
            }

            public hardware_location GetHardwareLocation(var v)
            { return GetHardwareLocation(v, 0); }

            public hardware_location GetHardwareLocation(var v, int size)
            {
                switch (v.type)
                {
                    case var.var_type.LocalArg:
                        return GetHardwareLocation(v.local_arg, size, v, largs);
                    case var.var_type.LocalVar:
                        return GetHardwareLocation(v.local_var, size, v, lvars);
                    case var.var_type.LogicalVar:
                        return GetHardwareLocation(v.logical_var, size, v, logs);
                    default:
                        throw new NotSupportedException();
                }
            }
        }

        public Assembler()
        {
            _requestor = new MemberRequestor(this);
            InitOpcodes();
            InitArchOpcodes();
            arch_init_callconvs();
        }

        public class AssemblyInformation
        {
            public string name;
            public byte[] pubkey;
            public Version version;
            public Metadata m;
        }

        List<AssemblyInformation> loaded_assemblies = new List<AssemblyInformation>();

        public Metadata FindAssembly(Metadata.AssemblyRefRow arr)
        {
            AssemblyInformation ai = ContainsAssembly(arr.Name, arr.PublicKeyOrToken, arr.Version);
            if (ai != null)
                return ai.m;

            Metadata m = Metadata.LoadAssembly(arr.Name, this);
            AddAssembly(m);
            return m;
        }

        public Metadata FindAssembly(string name)
        {
            AssemblyInformation ai = ContainsAssembly(name, null, null);
            if (ai != null)
                return ai.m;

            Metadata m = Metadata.LoadAssembly(name, this);
            return AddAssembly(m);
        }

        public Metadata AddAssembly(Metadata m)
        {
            Metadata m2 = ContainsAssembly(m);
            if (m2 != null)
                return m2;
            Metadata.AssemblyRow ar = (Metadata.AssemblyRow)m.Tables[(int)Metadata.TableId.Assembly][0];
            AssemblyInformation ai = new AssemblyInformation
            {
                m = m,
                pubkey = ar.PublicKey,
                name = ar.Name,
                version = ar.Version
            };
            loaded_assemblies.Add(ai);
            m.Information = ai;
            return m;
        }

        Metadata ContainsAssembly(Metadata m)
        {
            Metadata.AssemblyRow ar = (Metadata.AssemblyRow)m.Tables[(int)Metadata.TableId.Assembly][0];
            AssemblyInformation ai = ContainsAssembly(ar.Name, ar.PublicKey, ar.Version);
            if(ai == null)
                return null;
            else
                return ai.m;
        }

        Metadata ContainsAssembly(Metadata.AssemblyRefRow arr)
        {
            AssemblyInformation ai = ContainsAssembly(arr.Name, arr.PublicKeyOrToken, arr.Version);
            if (ai == null)
                return null;
            else
                return ai.m;
        }

        AssemblyInformation ContainsAssembly(string name, byte[] pubkey, Version version)
        {
            foreach (AssemblyInformation ai in loaded_assemblies)
            {
                if ((ai.name == name) || ((ai.name == "corlib") && (name == "mscorlib")))
                    return ai;

                // TODO check pubkey and version if not null
            }
            return null;
        }

        public List<AssemblyInformation> GetLoadedAssemblies()
        {
            return loaded_assemblies;
        }

        public class InstructionLine
        {
            public class PrefixesClass
            {
                public bool constrained = false;
                public Token constrained_tok = null;
                public bool no_typecheck = false;
                public bool no_rangecheck = false;
                public bool no_nullcheck = false;
                public bool read_only = false;
                public bool tail = false;
                public bool unaligned = false;
                public int unaligned_alignment = 0;
                public bool volatile_ = false;
            }

            public PrefixesClass Prefixes = new PrefixesClass();

            public int il_offset, il_offset_after;
            public Opcode opcode;
            public int inline_int;
            public long inline_int64;
            public double inline_dbl;
            public Token inline_tok;
            public List<int> inline_array = new List<int>();

            public int stack_before_adjust = 0;

            public bool start_block = false;
            public bool end_block = false;

            public List<int> il_offsets_after = new List<int>();

            public Signature.Param pushes;
            public var pushes_variable = var.Null;
            public int pop_count;

            public List<PseudoStack> stack_before, stack_after, lv_before, lv_after, la_before, la_after;
            public List<ThreeAddressCode> tacs = new List<ThreeAddressCode>();

            public override string ToString()
            {
                if (opcode != null)
                    return opcode.name;
                return base.ToString();
            }
        }

#if MT
        public void AssembleMethod(object mtcr) { AssembleMethod(mtcr as MTCRequest); }
        public void AssembleMethod(MTCRequest mtcr) { AssembleMethod(mtcr.mtc, mtcr.of); }
#endif
        public void AssembleMethod(MethodToCompile mtc, IOutputFile output)
        {
            Metadata.MethodDefRow meth = mtc.meth;
            Signature.BaseMethod call_site = mtc.msig;
            Metadata.TypeDefRow tdr = mtc.type;
            Signature.BaseOrComplexType tsig = mtc.tsig;
            Metadata m = meth.m;
            AssemblerState state = new AssemblerState();
            string mangled_name = Mangler.Mangle(mtc, this);

            state.reg_alloc = GetRegisterAllocator();

            if (mtc.meth.m.Information.name == "mscorlib")
                state.security = AssemblerSecurity.CorlibSecurity;

            foreach (Metadata.CustomAttributeRow car in m.Tables[(int)Metadata.TableId.CustomAttribute])
            {
                Metadata.MethodDefRow camdr = Metadata.GetMethodDef(car.Type.ToToken(), this);

                string caname = Mangler.Mangle(camdr, this);
                caname = caname.Substring(Mangler.Mangle(camdr.m).Length);
                if(caname == "_N3_ABI_T20_MethodAliasAttribute_Special_M5_.ctor_S8_2001010E")
                {
                    Metadata.MethodDefRow mdr = Metadata.GetMethodDef(car.Parent.ToToken(), this);
                    if (mdr == meth)
                    {
                        if (car.Value[0] != 0x01)
                            throw new NotSupportedException();
                        if (car.Value[1] != 0x00)
                            throw new NotSupportedException();
                        int offset = 2;
                        int len = Metadata.ReadCompressedInteger(car.Value, ref offset);
                        string s = new UTF8Encoding().GetString(car.Value, offset, len);
                        if(output != null)
                            output.AddTextSymbol(output.GetText().Count, s);
                    }
                }

                if (caname == "_N3_ABI_T22_ExtraArgumentAttribute_Special_M5_.ctor_S10_2002010808")
                {
                    Metadata.MethodDefRow mdr = Metadata.GetMethodDef(car.Parent.ToToken(), this);
                    if (mdr == meth)
                    {
                        if (car.Value[0] != 0x01)
                            throw new NotSupportedException();
                        if (car.Value[1] != 0x00)
                            throw new NotSupportedException();
                        int offset = 2;
                        int arg_no = (int)PEFile.Read32(car.Value, ref offset);
                        int arg_type = (int)PEFile.Read32(car.Value, ref offset);

                        if (!(mtc.msig is Signature.Method))
                            throw new NotSupportedException();
                        Signature.Method meth_sig = mtc.msig as Signature.Method;
                        while (meth_sig.Params.Count <= arg_no)
                            meth_sig.Params.Add(null);
                        meth_sig.Params[arg_no] = new Signature.Param((BaseType_Type)arg_type);
                    }
                }
            }

            if(call_site == null)
                call_site = Signature.ParseMethodDefSig(meth.m, meth.Signature);

            // Get the implementation
            byte[] impl = meth.Body.Body;
            if (impl == null)
            {
#if MT
                rtc--;
#endif
                return;
            }

            // Build a control graph
            List<cfg_node> nodes = BuildControlGraph(impl, m, state);

            // Create an IrDump for debugging purposes
            IrDump irdump = new IrDump(nodes);

            // Perform stack tracing and instruction encoding
            TraceStack(nodes, meth, m, mtc, mtc, state);

            nodes[0]._tacs_prephi.Insert(1, new ThreeAddressCode
            {
                Operator = ThreeAddressCode.Op.enter,
                Operand1 = new var { defined = false },
                Operand2 = new var { defined = false },
                Result = new var { defined = false }
            });

            // Identify global vars
            //  global vars dict links key = local var, value = global var
            Dictionary<int, int> global_vars_dict = new Dictionary<int, int>();
            IdentifyGlobalVars(nodes, ref global_vars_dict, state);
            // Rewrite the global variable references
            RewriteGlobalVars(nodes, global_vars_dict);
            
            // Populate Phi Functions
            //PopulatePhi(nodes);

            // Build a linear representation of the instruction stream to allow unnecessary jumps to be
            //  eliminated
            List<ThreeAddressCode> ir = new List<ThreeAddressCode>();
            foreach(cfg_node node in nodes)
                ir.AddRange(node.tacs);

            // Remove unnecessary jumps and phi functions
            List<int> global_vars = new List<int>();
            CleanseOutput(ir, global_vars);

            // Dump if requested
            if (IrFeedback != null)
            {
                lock (IrFeedback)
                {
                    IrFeedback.IrDump(irdump.Ir);
                }
            }

            // Exclude certain variables from the optimization passes (mostly global vars)
            ICollection<var> exclude_vars = GenerateExcludeVars(ir);

            // Perform variable folding
            VariableFolding(ir, exclude_vars);
        
            // Perform constant folding and propagation
            ConstantFolding(ir, exclude_vars);

            // Resolve phi instructions + generate list of global variables
            //ResolvePhis(ir, global_vars);
            GenerateGlobalVars(global_vars, global_vars_dict);

            // Perform TIR peephole optimizations
            TIRPeepholeOptimize(ir, nodes);

            // Reinsert the instruction stream into the basic blocks to enable liveness analysis
            ReinsertIR(ir, nodes);

            // Insert a pseudo-end node if there is more than one end node
            cfg_node end = InsertPseudoEnd(nodes, state);

            // Loop through the code, adding and removing insturctions as necessary to try and get it to compile
            int iterations = 0;
            bool changes_made = false;
            IList<OutputBlock> blocks = null;

            if (mtc.meth.Name == "ToString")
            {
                int asgfdsg = 0;
            }


#if DEBUG
            List<ThreeAddressCode> faulting_instructions = new List<ThreeAddressCode>();
#else
                List<ThreeAddressCode> faulting_instructions = null;
#endif

            do
            {
                changes_made = false;
                int remove_count = 0;
                do
                {
                    // Perform liveness analysis for variables
                    LivenessAnalysis(end, nodes);

                    // Remove code that assigns to dead variables
                    remove_count = RemoveDeadCode(nodes);
                } while (remove_count > 0);

                // Some instructions below still work on a linear ir stream, therefore recreate it
                ir = new List<ThreeAddressCode>();
                foreach (cfg_node node in nodes)
                {
                    if (node.optimized_ir != null)
                        ir.AddRange(node.optimized_ir);
                }

                // Do arch specific changes
                ArchSpecific(ir, nodes, state);

                // Determine the semantics of the variables
                Dictionary<int, var_semantic> semantics = DetermineSemantics(ir);

                // Add in extra global vars which have been missed by previous passes
                //AddExtraGlobalVars(global_vars, nodes);

                // Remove global vars which have been optimized away
                RemoveUnnecessaryGlobalVars(global_vars, semantics);

                // Determine the preferred locations of certain variables
                //Dictionary<int, hloc_constraint> preferred_locs = GeneratePreferredLocations(ir, semantics);

                // Generate a coloured register graph
                RegisterGraph big_rg = GenerateRegisterGraph(nodes, state);
                List<RegisterGraph> rgs = big_rg.SplitGraph();

                foreach (RegisterGraph rg in rgs)
                {
                    // Precolour the graph
                    PrecolourGraph(rg, state, semantics, ref changes_made, faulting_instructions);
                    if (changes_made)
                        break;

                    // Do register allocation
                    ColourGraph(rg, semantics);
                }

                // Do actual code generation
                if(!changes_made)
                    blocks = GenerateCode(nodes, big_rg, state, semantics, ref changes_made, faulting_instructions);

                iterations++;

                if (iterations == 50)
                    throw new TooManyIterationsException();
            } while (changes_made);

            // Add in arch specific code if neccessary
            ArchSpecificCode(state, blocks);

            // Resolve local jumps
            List<RelocationBlock> rbs = new List<RelocationBlock>();
            IList<byte> b = ResolveLocals(blocks, rbs);

            if (output != null)
            {
                lock (output)
                {
                    int meth_offset = output.GetText().Count;
                    //output.AddTextSymbol(output.GetText().Count, Mangler.Mangle(tdr, new Signature.Param(tsig, this), meth, call_site, this));
                    output.AddTextSymbol(output.GetText().Count, mangled_name);

                    foreach (byte bb in b)
                        output.GetText().Add(bb);
                    foreach (RelocationBlock rb in rbs)
                        output.AddTextRelocation(rb.Offset + meth_offset, rb.Target, rb.RelType, rb.Value);
                }
            }

            if (this.ProduceIrDump == true)
            {
                _irdump += Mangler.Mangle(tdr, new Signature.Param(tsig, this), meth, call_site, this);
                _irdump += ":";
                _irdump += Environment.NewLine;
                _irdump += "global vars: ";
                foreach(int gv in global_vars)
                    _irdump += gv.ToString() + " ";
                _irdump += Environment.NewLine;
                _irdump += irdump.Ir;
                _irdump += Environment.NewLine;
            }

            if (OptimizedIrFeedback != null)
            {
                lock (OptimizedIrFeedback)
                {
                    OptimizedIrFeedback.IrDump(irdump.Ir);
                }
            }

#if MT
            rtc--;
#endif
        }

        private void TIRPeepholeOptimize(List<ThreeAddressCode> ir, List<cfg_node> nodes)
        {
            // Perform various peephole optimizations on the tir code
            for (int i = 0; i < ir.Count; i++)
            {
                ThreeAddressCode inst = ir[i];

                if (inst.GetOpType() == ThreeAddressCode.OpType.AssignOp)
                {
                    /* Some combinations of instructions take the form:
                     * 
                     * 1 = &la1/lv1 (using assign_vt)
                     * 2 = [1 + $const]
                     * 
                     * These can be optimised to:
                     * 
                     * 1 = &la1/lv1
                     * 2 = [la1/lv1 + $const]
                     * 
                     * and then the first command may be optimised away be the liveness analysis pass if no longer needed later on
                     */

                    if ((inst.Operand1.type == var.var_type.ContentsOfAddress) ||
                        (inst.Operand1.type == var.var_type.ContentsOfAddressPlusConstant))
                    {
                        if (i > 0)
                        {
                            ThreeAddressCode prev_inst = ir[i - 1];

                            if ((prev_inst.GetOpType() == ThreeAddressCode.OpType.AssignOp) &&
                                ((prev_inst.Operand1.type == var.var_type.AddressOfLocalArg) ||
                                (prev_inst.Operand1.type == var.var_type.AddressOfLocalVar)))
                            {
                                inst.Operand1.logical_var = 0;

                                if (prev_inst.Operand1.type == var.var_type.AddressOfLocalArg)
                                    inst.Operand1.local_arg = prev_inst.Operand1.local_arg;
                                else
                                    inst.Operand1.local_var = prev_inst.Operand1.local_var;
                            }
                        }
                    }
                }
                else if (inst.GetOpType() == ThreeAddressCode.OpType.ReturnOp)
                {
                    /* A lot of methods end:
                     * 
                     * lva = b
                     * L2:
                     * ret(lva)
                     * 
                     * These can be optimised to:
                     * 
                     * L2:
                     * ret(b)
                     * 
                     * as long as there is only one entry point into node '2'
                     */

                    if (i >= 2)
                    {
                        ThreeAddressCode l1 = ir[i - 2];
                        ThreeAddressCode l2 = ir[i - 1];
                        ThreeAddressCode l3 = ir[i];

                        if ((l1.GetOpType() == ThreeAddressCode.OpType.AssignOp) &&
                            (l1.Result.type == var.var_type.LocalVar))
                        {
                            var b = l1.Operand1;
                            int lv_no = l1.Result.local_var;

                            if((l2.Operator == ThreeAddressCode.Op.label) &&
                                (nodes[((LabelEx)l2).Block_id - 1].ipred.Count == 1))
                            {
                                if ((l3.GetOpType() == ThreeAddressCode.OpType.ReturnOp) &&
                                    (l3.Operand1.type == var.var_type.LocalVar) &&
                                    (l3.Operand1.local_var == lv_no))
                                {
                                    l3.Operand1 = b;

                                    ir.RemoveAt(i - 2);
                                    i--;
                                }
                            }
                        }
                    }
                }
            }
        }

        protected virtual void ArchSpecificCode(AssemblerState state, IList<OutputBlock> blocks)
        { }

        private void GenerateGlobalVars(List<int> global_vars, Dictionary<int, int> global_vars_dict)
        {
            foreach (KeyValuePair<int, int> kvp in global_vars_dict)
            {
                if (!global_vars.Contains(kvp.Value))
                    global_vars.Add(kvp.Value);
            }
        }

        private void RewriteGlobalVars(List<cfg_node> nodes, Dictionary<int, int> global_vars_dict)
        {
            foreach (cfg_node node in nodes)
            {
                foreach (ThreeAddressCode tac in node.tacs)
                {
                    RewriteGlobalVars(ref tac.Operand1, global_vars_dict);
                    RewriteGlobalVars(ref tac.Operand2, global_vars_dict);
                    RewriteGlobalVars(ref tac.Result, global_vars_dict);
                    if (tac is CallEx)
                    {
                        CallEx ce = tac as CallEx;
                        for(int i = 0; i < ce.Var_Args.Length; i++)
                            RewriteGlobalVars(ref ce.Var_Args[i], global_vars_dict);
                    }
                }
            }
        }

        private void RewriteGlobalVars(ref var var, Dictionary<int, int> global_vars_dict)
        {
            if ((var.logical_var != 0) && (global_vars_dict.ContainsKey(var.logical_var)))
            {
                var.logical_var = global_vars_dict[var.logical_var];
            }
        }

        private void IdentifyGlobalVars(List<cfg_node> nodes, ref Dictionary<int, int> global_vars_dict, AssemblerState state)
        {
            foreach (cfg_node node in nodes)
            {
                // match up the vars in the stack at the end of this block with the vars in the stack at the
                //  start of the next ones

                if (!node.stack_traced)
                    continue;

                for (int i = 0; i < node.pstack_after.Count; i++)
                {
                    int cur_gv = -1;

                    if (global_vars_dict.ContainsKey(node.pstack_after[i].contains_variable))
                        cur_gv = global_vars_dict[node.pstack_after[i].contains_variable];
                    else if (node.pstack_after[i].contains_variable == 0)
                        continue;
                    else
                    {
                        cur_gv = state.next_variable++;
                        global_vars_dict[node.pstack_after[i].contains_variable] = cur_gv;
                    }

                    foreach (cfg_node suc in node.isuc)
                    {
                        if (global_vars_dict.ContainsKey(suc.pstack_before[i].contains_variable))
                        {
                            if (global_vars_dict[suc.pstack_before[i].contains_variable] != cur_gv)
                            {
                                // rewrite all usages of the previous global var to use the current one
                                int refactor_var = global_vars_dict[suc.pstack_before[i].contains_variable];
                                Dictionary<int, int> new_gvd = new Dictionary<int, int>();
                                foreach (KeyValuePair<int, int> kvp in global_vars_dict)
                                {
                                    if (kvp.Value == refactor_var)
                                        new_gvd.Add(kvp.Key, cur_gv);
                                    else
                                        new_gvd.Add(kvp.Key, kvp.Value);
                                }
                                global_vars_dict = new_gvd;
                            }
                        }
                        else
                        {
                            /* propagate the global var further on */
                            global_vars_dict[suc.pstack_before[i].contains_variable] = cur_gv;
                        }
                    }
                }
            }
        }

        private void AddExtraGlobalVars(List<int> global_vars, List<cfg_node> nodes)
        {
            List<int> used_already = new List<int>();
            foreach (cfg_node node in nodes)
            {
                foreach (int v in node.all_used_vars)
                {
                    if (used_already.Contains(v))
                    {
                        if (!global_vars.Contains(v))
                            global_vars.Add(v);
                    }
                    else
                        used_already.Add(v);
                }
            }
        }

        private void RemoveUnnecessaryGlobalVars(List<int> global_vars, Dictionary<int, var_semantic> semantics)
        {
            int i = 0;
            while(i < global_vars.Count)
            {
                if (!semantics.ContainsKey(global_vars[i]))
                    global_vars.RemoveAt(i);
                else
                    i++;
            }
        }

        private ICollection<var> GenerateExcludeVars(List<ThreeAddressCode> ir)
        {
            List<var> ev = new List<var>();
            List<var> assigned_vars = new List<var>();
            foreach (ThreeAddressCode i in ir)
            {
                if (i.Result.type == var.var_type.LogicalVar)
                {
                    if ((assigned_vars.Contains(i.Result)) && (!ev.Contains(i.Result)))
                        ev.Add(i.Result);
                    else
                        assigned_vars.Add(i.Result);
                }
            }
            return ev;
        }

        private int RemoveDeadCode(List<cfg_node> nodes)
        {
            int remove_count = 0;
            foreach (cfg_node node in nodes)
            {
                int i = 0;
                if (node.optimized_ir != null)
                {
                    while (i < node.optimized_ir.Count)
                    {
                        if ((node.optimized_ir[i].Result.logical_var != 0) && 
                            (node.optimized_ir[i].Result.contents_of == false))
                        {
                            bool can_remove = true;
                            foreach (var v in node.optimized_ir[i].live_vars_after)
                            {
                                if (v.logical_var == node.optimized_ir[i].Result.logical_var)
                                {
                                    can_remove = false;
                                    break;
                                }
                            }

                            if (can_remove)
                            {
                                if (node.optimized_ir[i].GetOpType() == ThreeAddressCode.OpType.CallOp)
                                {
                                    node.optimized_ir[i].Operator = ThreeAddressCode.Op.call_void;
                                    node.optimized_ir[i].Result = new var { defined = false };
                                }
                                else
                                {
                                    node.optimized_ir.RemoveAt(i);
                                    remove_count++;
                                    continue;
                                }
                            }
                        }

                        // Also remove code like 1 = assign(1)
                        if (node.optimized_ir[i].GetOpType() == ThreeAddressCode.OpType.AssignOp)
                        {
                            if ((node.optimized_ir[i].Result.type == var.var_type.LogicalVar) &&
                                (node.optimized_ir[i].Operand1.type == var.var_type.LogicalVar) &&
                                (node.optimized_ir[i].Result == node.optimized_ir[i].Operand1))
                            {
                                node.optimized_ir.RemoveAt(i);
                                remove_count++;
                                continue;
                            }
                        }
                        i++;
                    }
                }
            }
            return remove_count;
        }

        private void VariableFolding(List<ThreeAddressCode> ir, ICollection<var> exclude_vars)
        {
            Dictionary<int, var> vars = new Dictionary<int, var>();
            Dictionary<string, int> const_vars = new Dictionary<string, int>();

            int i = 0;

            i = 0;
            while(i < ir.Count)
            {
                ThreeAddressCode inst = ir[i];
                if (const_vars.ContainsKey(inst.Operand1.ToString()))
                    inst.Operand1 = const_vars[inst.Operand1.ToString()];
                if (inst.Operand1.type == var.var_type.LogicalVar)
                {
                    if (vars.ContainsKey(inst.Operand1.logical_var))
                        inst.Operand1 = vars[inst.Operand1.logical_var].CloneVar();
                }
                if (inst.Operand2.type == var.var_type.LogicalVar)
                {
                    if (vars.ContainsKey(inst.Operand2.logical_var))
                        inst.Operand2 = vars[inst.Operand2.logical_var].CloneVar();
                }
                if (inst.GetOpType() == ThreeAddressCode.OpType.CallOp)
                {
                    CallEx ce = inst as CallEx;
                    for(int j = 0; j < ce.Var_Args.Length; j++)
                    {
                        if(vars.ContainsKey(ce.Var_Args[j].logical_var))
                            ce.Var_Args[j] = vars[ce.Var_Args[j].logical_var].CloneVar();
                    }
                }
                if (inst.GetOpType() == ThreeAddressCode.OpType.AssignOp)
                {
                    if (inst.Result.type == var.var_type.LogicalVar)
                    {
                        switch (inst.Operand1.type)
                        {
                            case var.var_type.Const:
                            case var.var_type.LogicalVar:
                                if (!exclude_vars.Contains(inst.Result))
                                {
                                    vars.Add(inst.Result.logical_var, inst.Operand1);
                                    if (CanRemove(inst.Result.logical_var, ir))
                                    {
                                        ir.RemoveAt(i);
                                        continue;
                                    }
                                }
                                break;

                            case var.var_type.AddressOfLabel:
                            case var.var_type.AddressOfLabelPlusConstant:
                            case var.var_type.AddressOfLocalArg:
                            case var.var_type.AddressOfLocalVar:
                                if (!exclude_vars.Contains(inst.Result))
                                {
                                    vars.Add(inst.Result.logical_var, inst.Operand1);
                                    if(!const_vars.ContainsKey(inst.Operand1.ToString()))
                                        const_vars.Add(inst.Operand1.ToString(), inst.Result.logical_var);
                                }
                                break;
                        }
                    }
                }
                i++;
            }
        }

        struct jmp_source { public int target_block; public int src_oblock; public int src_suboblock; 
            public int src_suboblock2; public int src_offset;
            public int offset; public int length;
        }

        private IList<byte> ResolveLocals(IList<OutputBlock> blocks, IList<RelocationBlock> rbs)
        {
            // Assume that the distance between a jump and its destination is the longest possible jump (based
            //  upon BlockChoices), then choose the appropriate block choices, and then fill in the actual
            //  distances

            // Fisrt, identify a list of jump sources and destinations
            Dictionary<int, int> header_list_blockid_obi = new Dictionary<int, int>();
            List<jmp_source> jmp_list = new List<jmp_source>();

            for (int i = 0; i < blocks.Count; i++)
            {
                if (blocks[i] is NodeHeader)
                    header_list_blockid_obi.Add(((NodeHeader)blocks[i]).block_id, i);
                if (blocks[i] is NodeReference)
                    jmp_list.Add(new jmp_source { src_oblock = i, target_block = ((NodeReference)blocks[i]).block_id });
                if (blocks[i] is BlockChoice)
                {
                    for(int j = 0; j < ((BlockChoice)blocks[i]).Choices.Count; j++)
                    {
                        IList<OutputBlock> obs2 = ((BlockChoice)blocks[i]).Choices[j];
                        for (int k = 0; k < obs2.Count; k++)
                        {
                            OutputBlock ob2 = obs2[k];
                            if (ob2 is NodeHeader)
                                header_list_blockid_obi.Add(((NodeHeader)ob2).block_id, i);
                            if (ob2 is NodeReference)
                                jmp_list.Add(new jmp_source { src_oblock = i, target_block = ((NodeReference)ob2).block_id,
                                    src_suboblock = j, src_suboblock2 = k });
                        }
                    }
                }
            }

            // Now, for each jump work out the largest possible distance that needs to be crossed
            foreach (jmp_source js in jmp_list)
            {
                int source_block = js.src_oblock;
                int dest_block = header_list_blockid_obi[js.target_block];
                int offset = GetNodeReference(blocks, js).offset;
                int dir = (dest_block > source_block) ? 1 : -1;

                int dist = offset;

                for (int i = source_block; i != dest_block; i += dir)
                    dist += GetLongestBlockLength(blocks[i]) * dir;

                GetNodeReference(blocks, js).longest_distance = dist;
            }

            // Make a decision as to the shortest of the various block choices to use
            int cur_block = 0;
            while (cur_block < blocks.Count)
            {
                if (blocks[cur_block] is BlockChoice)
                {
                    IList<OutputBlock> best_subblock = null;
                    int best_subblock_length = Int32.MaxValue;
                    for (int cur_subblock = 0; cur_subblock < ((BlockChoice)blocks[cur_block]).Choices.Count;
                        cur_subblock++)
                    {
                        if(IsBlockChoicePossible(((BlockChoice)blocks[cur_block]).Choices[cur_subblock]))
                        {
                            int length_of = GetLongestBlockLength(((BlockChoice)blocks[cur_block]).Choices[cur_subblock]);
                            if (length_of < best_subblock_length)
                            {
                                best_subblock_length = length_of;
                                best_subblock = ((BlockChoice)blocks[cur_block]).Choices[cur_subblock];
                            }
                        }
                    }

                    if (best_subblock == null)
                        throw new Exception("no subblock possible");

                    // replace the block choice with the best subblock
                    blocks.RemoveAt(cur_block);
                    for (int cur_subblock2 = 0; cur_subblock2 < best_subblock.Count; cur_subblock2++)
                        blocks.Insert(cur_block + cur_subblock2, best_subblock[cur_subblock2]);
                }

                cur_block++;
            }

            // Now turn the output block stream into a linear byte array with the addresses of headers and
            //  references saved
            List<byte> b = new List<byte>();
            Dictionary<int, int> d_headerid_offset = new Dictionary<int, int>();
            List<jmp_source> l_js = new List<jmp_source>();

            foreach (OutputBlock ob in blocks)
            {
                if (ob is CodeBlock)
                    b.AddRange(((CodeBlock)ob).Code);
                else if (ob is NodeReference)
                {
                    l_js.Add(new jmp_source { target_block = ((NodeReference)ob).block_id, src_offset = b.Count,
                        offset = ((NodeReference)ob).offset, length = ((NodeReference)ob).length });
                    for (int c = 0; c < ((NodeReference)ob).length; c++)
                        b.Add(0);
                }
                else if (ob is NodeHeader)
                    d_headerid_offset.Add(((NodeHeader)ob).block_id, b.Count);
                else if (ob is RelocationBlock)
                {
                    RelocationBlock rb = ob as RelocationBlock;
                    rb.Offset = b.Count;
                    rbs.Add(rb);
                    
                    for (int c = 0; c < ((RelocationBlock)ob).Size; c++)
                        b.Add(0);
                }
            }

            foreach (jmp_source js in l_js)
            {
                int rel_offset = d_headerid_offset[js.target_block] - js.src_offset + js.offset;

                NodeReference nr = new NodeReference { length = js.length, longest_distance = rel_offset };
                if (nr.IsPossible == false)
                    throw new Exception("relocation needs to be truncated to fit");

                this.SetByteArray(b, js.src_offset, rel_offset, js.length);
            }

            return b;
        }

        private bool IsBlockChoicePossible(IList<OutputBlock> iList)
        {
            foreach (OutputBlock ob in iList)
            {
                if (ob is NodeReference)
                    if (((NodeReference)ob).IsPossible == false)
                        return false;
            }
            return true;
        }

        private int GetLongestBlockLength(OutputBlock outputBlock)
        {
            if (outputBlock is CodeBlock)
                return ((CodeBlock)outputBlock).Code.Count;
            if (outputBlock is NodeReference)
                return ((NodeReference)outputBlock).length;
            if (outputBlock is NodeHeader)
                return 0;
            if (outputBlock is BlockChoice)
            {
                int longest = 0;
                foreach (List<OutputBlock> list_obs in ((BlockChoice)outputBlock).Choices)
                {
                    int cur_length = GetLongestBlockLength(list_obs);
                    if (cur_length > longest)
                        longest = cur_length;
                }
                return longest;
            }
            if (outputBlock is RelocationBlock)
                return ((RelocationBlock)outputBlock).Size;
            throw new NotSupportedException();
        }

        protected int GetLongestBlockLength(IList<OutputBlock> list_obs)
        {
            int cur_length = 0;
            foreach (OutputBlock ob in list_obs)
                cur_length += GetLongestBlockLength(ob);
            return cur_length;
        }

        private NodeReference GetNodeReference(IList<OutputBlock> blocks, jmp_source js)
        {
            if (blocks[js.src_oblock] is BlockChoice)
                return ((BlockChoice)blocks[js.src_oblock]).Choices[js.src_suboblock][js.src_suboblock2] as NodeReference;
            return blocks[js.src_oblock] as NodeReference;
        }

        internal static var_semantic GetSemantic(CliType ct, int? vt_size)
        {
            var_semantic vs = new var_semantic();

            switch (ct)
            {
                case Assembler.CliType.F:
                    vs.needs_float64 = true;
                    vs.vtype_size = 8;
                    break;
                case Assembler.CliType.int32:
                    vs.needs_int32 = true;
                    vs.vtype_size = 4;
                    break;
                case Assembler.CliType.int64:
                    vs.needs_int64 = true;
                    vs.vtype_size = 8;
                    break;
                case Assembler.CliType.native_int:
                case Assembler.CliType.O:
                case Assembler.CliType.reference:
                    vs.needs_intptr = true;
                    vs.vtype_size = 8;
                    break;
                case Assembler.CliType.vt:
                    vs.needs_vtype = true;
                    if ((!vt_size.HasValue) || (vt_size.Value == 0))
                        throw new Exception("VTSize not defined");
                    vs.vtype_size = vt_size.Value;
                    break;
            }
            return vs;
        }

        private Dictionary<int, var_semantic> DetermineSemantics(List<ThreeAddressCode> ir)
        {
            Dictionary<int, var_semantic> semantics = new Dictionary<int, var_semantic>();

            foreach (ThreeAddressCode inst in ir)
            {
                int var = inst.Result;
                if (var == 26)
                {
                    int sdfhghsd = 0;
                }
                if (var > 0)
                {
                    var_semantic semantic = inst.GetResultSemantic();

                    if (semantics.ContainsKey(var))
                    {
                        // Merge the semantics
                        semantics[var].Merge(semantic);
                    }
                    else
                        semantics.Add(var, semantic);
                }
            }

            return semantics;
        }

        protected cfg_node InsertPseudoEnd(List<cfg_node> nodes, AssemblerState state)
        {
            List<cfg_node> end_nodes = new List<cfg_node>();

            for(int i = 0; i < nodes.Count; i++)
            {
                if (nodes[i].isuc.Count == 0)
                    end_nodes.Add(nodes[i]);
            }

            if (end_nodes.Count == 1)
                return end_nodes[0];
            if (end_nodes.Count == 0)
                throw new Exception("no end node identified");

            cfg_node end_node = new cfg_node(state.next_block++);
            end_node.optimized_ir = new List<ThreeAddressCode>();
            foreach(cfg_node n in end_nodes)
            {
                n.isuc.Add(end_node);
                end_node.ipred.Add(n);
            }
            nodes.Add(end_node);
            return end_node;
        }

        protected void LivenessAnalysis(cfg_node end_node, List<cfg_node> nodes)
        {
            end_node.live_vars_at_end.Clear();

            foreach (cfg_node n in nodes)
            {
                n.live_vars_at_end.Clear();
                n.live_vars_done = false;
            }

            List<var> live_vars = new List<var>();
            LivenessAnalysis(live_vars, end_node);
        }

        private void LivenessAnalysis(List<var> live_vars, cfg_node cur_node)
        {
            if (cur_node.optimized_ir != null)
            {
                for (int i = cur_node.optimized_ir.Count - 1; i >= 0; i--)
                {
                    cur_node.optimized_ir[i].live_vars = new List<var>(live_vars);
                    cur_node.optimized_ir[i].live_vars_after = new List<var>(live_vars);

                    // Remove vars written to from live, then add vars read from
                    // Note that assigning to references like *1 and &1 are actually reading from 1
                    if (cur_node.optimized_ir[i].Result.type == var.var_type.LogicalVar)
                    {
                        if (cur_node.optimized_ir[i].live_vars.Contains(cur_node.optimized_ir[i].Result.logical_var))
                            cur_node.optimized_ir[i].live_vars.Remove(cur_node.optimized_ir[i].Result.logical_var);
                    }
                    else if ((cur_node.optimized_ir[i].Result.logical_var != 0) &&
                        !cur_node.optimized_ir[i].live_vars.Contains(cur_node.optimized_ir[i].Result.logical_var))
                        cur_node.optimized_ir[i].live_vars.Add(cur_node.optimized_ir[i].Result.logical_var);

                    if ((cur_node.optimized_ir[i].Operand1.logical_var != 0) &&
                        !cur_node.optimized_ir[i].live_vars.Contains(cur_node.optimized_ir[i].Operand1.logical_var))
                        cur_node.optimized_ir[i].live_vars.Add(cur_node.optimized_ir[i].Operand1.logical_var);
                    if ((cur_node.optimized_ir[i].Operand2.logical_var != 0) &&
                        !cur_node.optimized_ir[i].live_vars.Contains(cur_node.optimized_ir[i].Operand2.logical_var))
                        cur_node.optimized_ir[i].live_vars.Add(cur_node.optimized_ir[i].Operand2.logical_var);
                    if (cur_node.optimized_ir[i] is CallEx)
                    {
                        CallEx ce = cur_node.optimized_ir[i] as CallEx;
                        foreach (int p in ce.Var_Args)
                        {
                            if ((p != 0) && !cur_node.optimized_ir[i].live_vars.Contains(p))
                                cur_node.optimized_ir[i].live_vars.Add(p);
                            if ((p != 0) && !cur_node.all_used_vars.Contains(p))
                                cur_node.all_used_vars.Add(p);
                        }
                    }

                    ThreeAddressCode inst = cur_node.optimized_ir[i];
                    if ((inst.Result.logical_var != 0) && (!cur_node.all_used_vars.Contains(inst.Result.logical_var)))
                        cur_node.all_used_vars.Add(inst.Result.logical_var);
                    if ((inst.Operand1.logical_var != 0) && (!cur_node.all_used_vars.Contains(inst.Operand1.logical_var)))
                        cur_node.all_used_vars.Add(inst.Operand1.logical_var);
                    if ((inst.Operand2.logical_var != 0) && (!cur_node.all_used_vars.Contains(inst.Operand2.logical_var)))
                        cur_node.all_used_vars.Add(inst.Operand2.logical_var);


                    live_vars = cur_node.optimized_ir[i].live_vars;
                }
            }

            cur_node.live_vars_done = true;
            cur_node.live_vars_at_start = live_vars;

            // Process predecessor nodes if there are extra live vars to do
            foreach (cfg_node pred in cur_node.ipred)
            {
                bool new_added = false;

                foreach (int i in live_vars)
                {
                    if (!pred.live_vars_at_end.Contains(i))
                    {
                        pred.live_vars_at_end.Add(i);
                        new_added = true;
                    }
                }

                if ((!pred.live_vars_done) || new_added)
                    LivenessAnalysis(pred.live_vars_at_end, pred);
            }
        }

        private void ReinsertIR(List<ThreeAddressCode> ir, List<cfg_node> nodes)
        {
            cfg_node cur_node = null;

            for (int i = 0; i < ir.Count; i++)
            {
                if (ir[i] is LabelEx)
                {
                    int cur_label = ((LabelEx)ir[i]).Block_id;
                    cur_node = GetNode(cur_label, nodes);
                    if (cur_node == null)
                        throw new Exception("node not found");
                    cur_node.optimized_ir = new List<ThreeAddressCode>();
                }

                if (cur_node != null)
                {
                    cur_node.optimized_ir.Add(ir[i]);
                    ir[i].node = cur_node;
                }
            }
        }

        private cfg_node GetNode(int cur_label, List<cfg_node> nodes)
        {
            for (int i = 0; i < nodes.Count; i++)
            {
                if (nodes[i].block_id == cur_label)
                    return nodes[i];
            }
            return null;
        }

        private void ResolvePhis(List<ThreeAddressCode> ir, List<int> global_vars)
        {
            // a dictionary <act_var, phi_var> that links variables to the phi variable they affect
            Dictionary<int, int> phis = new Dictionary<int, int>();

            // identify and remove the phi instructions
            int i = 0;
            while (i < ir.Count)
            {
                if (ir[i] is PhiEx)
                {
                    PhiEx phi = ir[i] as PhiEx;
                    foreach (int var in phi.Var_Args)
                        phis.Add(var, ir[i].Result);
                    ir.RemoveAt(i);
                    continue;
                }
                i++;
            }

            // replace all references to the phis
            foreach (ThreeAddressCode inst in ir)
            {
                if (phis.ContainsKey(inst.Result))
                    inst.Result = phis[inst.Result];
                if (phis.ContainsKey(inst.Operand1))
                    inst.Operand1 = phis[inst.Operand1];
                if (phis.ContainsKey(inst.Operand2))
                    inst.Operand2 = phis[inst.Operand2];

                if (inst is CallEx)
                {
                    CallEx ce = inst as CallEx;
                    int j;
                    for (j = 0; j < ce.Var_Args.Length; j++)
                    {
                        if (phis.ContainsKey(ce.Var_Args[j]))
                            ce.Var_Args[j] = phis[ce.Var_Args[j]];
                    }
                }
            }

            // Generate a list of all the global variables (those which cross basic block boundaries)
            foreach(KeyValuePair<int, int> kvp in phis)
            {
                if (!global_vars.Contains(kvp.Value))
                    global_vars.Add(kvp.Value);
            }
        }

        private void ConstantFolding(List<ThreeAddressCode> ir, ICollection<var> exclude_vars)
        {
            Dictionary<int, object> constant_vals = new Dictionary<int, object>();

            int i = 0;
            while (i < ir.Count)
            {
                ThreeAddressCode.OpType optype = ir[i].GetOpType();
                if (optype == ThreeAddressCode.OpType.ConstOp)
                {
                    constant_vals.Add(ir[i].Result, ir[i].Result.constant_val);
                    if (CanRemove(ir[i].Result, ir))
                    {
                        ir.RemoveAt(i);
                        continue;
                    }
                    else
                        ir[i].Operator = GetAssignTac(ir[i].Operator);
                }
                if (optype == ThreeAddressCode.OpType.BinNumOp)
                {
                    if (ir[i].Operand1.type == var.var_type.LogicalVar)
                    {
                        if (constant_vals.ContainsKey(ir[i].Operand1))
                        {
                            ir[i].Operand1.constant_val = constant_vals[ir[i].Operand1];
                            ir[i].Operand1.logical_var = 0;
                        }
                    }
                    if (ir[i].Operand2.type == var.var_type.LogicalVar)
                    {
                        if (constant_vals.ContainsKey(ir[i].Operand2))
                        {
                            ir[i].Operand2.constant_val = constant_vals[ir[i].Operand2];
                            ir[i].Operand2.logical_var = 0;
                        }
                    }

                    if ((ir[i].Operand1.constant_val != null) && (ir[i].Operand2.constant_val != null))
                    {
                        if (EvaluateConstant(ir[i]))
                        {
                            if (ir[i].Result.type == var.var_type.LogicalVar)
                            {
                                ir[i].Result = ir[i].Operand1;
                                if (!exclude_vars.Contains(ir[i].Result))
                                {
                                    constant_vals.Add(ir[i].Result, ir[i].Result.constant_val);
                                    if (CanRemove(ir[i].Result, ir))
                                    {
                                        ir.RemoveAt(i);
                                        continue;
                                    }
                                }
                            }
                        }
                    }
                }
                if (optype == ThreeAddressCode.OpType.CmpOp)
                {
                    if (constant_vals.ContainsKey(ir[i].Operand1))
                    {
                        ir[i].Operand1.constant_val = constant_vals[ir[i].Operand1];
                        ir[i].Operand1.logical_var = 0;
                    }
                    if (constant_vals.ContainsKey(ir[i].Operand2))
                    {
                        ir[i].Operand2.constant_val = constant_vals[ir[i].Operand2];
                        ir[i].Operand2.logical_var = 0;
                    }
                }
                if ((optype == ThreeAddressCode.OpType.InternalOp) || (optype == ThreeAddressCode.OpType.OtherOptimizeOp) || (optype == ThreeAddressCode.OpType.ReturnOp))
                {
                    if (constant_vals.ContainsKey(ir[i].Operand1))
                    {
                        ir[i].Operand1.constant_val = constant_vals[ir[i].Operand1];
                        ir[i].Operand1.logical_var = 0;
                    }
                    if (constant_vals.ContainsKey(ir[i].Operand2))
                    {
                        ir[i].Operand2.constant_val = constant_vals[ir[i].Operand2];
                        ir[i].Operand2.logical_var = 0;
                    }
                }
                if (optype == ThreeAddressCode.OpType.AssignOp)
                {
                    if ((ir[i].Operand1.type == var.var_type.Const) &&
                        (ir[i].Result.type == var.var_type.LogicalVar))
                    {
                        if (!exclude_vars.Contains(ir[i].Result))
                        {
                            constant_vals.Add(ir[i].Result, ir[i].Operand1.constant_val);
                            if (CanRemove(ir[i].Result, ir))
                            {
                                ir.RemoveAt(i);
                                continue;
                            }
                        }
                    }
                    else if ((ir[i].Operand1.type == var.var_type.LogicalVar) &&
                        (ir[i].Result.type == var.var_type.LogicalVar))
                    {
                        if (constant_vals.ContainsKey(ir[i].Operand1))
                        {
                            ir[i].Operand1.constant_val = constant_vals[ir[i].Operand1];
                            ir[i].Operand1.logical_var = 0;
                            if (!exclude_vars.Contains(ir[i].Result))
                            {
                                constant_vals.Add(ir[i].Result, ir[i].Operand1.constant_val);
                                if (CanRemove(ir[i].Result, ir))
                                {
                                    ir.RemoveAt(i);
                                    continue;
                                }
                            }
                        }
                    }
                    else if (ir[i].Operand1.type == var.var_type.LogicalVar)
                    {
                        if (constant_vals.ContainsKey(ir[i].Operand1))
                        {
                            ir[i].Operand1.constant_val = constant_vals[ir[i].Operand1];
                            ir[i].Operand1.logical_var = 0;
                        }
                    }
                }
                if ((optype == ThreeAddressCode.OpType.UnNumOp) || (optype == ThreeAddressCode.OpType.ConvOp))
                {
                    if (constant_vals.ContainsKey(ir[i].Operand1))
                    {
                        ir[i].Operand1.constant_val = constant_vals[ir[i].Operand1];
                        ir[i].Operand1.logical_var = 0;
                    }
                    if(ir[i].Operand1.constant_val != null)
                    {
                        if (EvaluateConstant(ir[i]))
                        {
                            if ((ir[i].Result.type == var.var_type.LogicalVar) && !exclude_vars.Contains(ir[i].Result))
                            {
                                constant_vals.Add(ir[i].Result, ir[i].Operand1.constant_val);
                                if (CanRemove(ir[i].Result, ir))
                                {
                                    ir.RemoveAt(i);
                                    continue;
                                }
                            }
                        }
                    }
                }
                if (optype == ThreeAddressCode.OpType.CallOp)
                {
                    CallEx ce = ir[i] as CallEx;
                    for(int j = 0; j < ce.Var_Args.Length; j++)
                    {
                        if (constant_vals.ContainsKey(ce.Var_Args[j]))
                        {
                            ce.Var_Args[j].constant_val = constant_vals[ce.Var_Args[j]];
                            ce.Var_Args[j].logical_var = 0;
                        }
                    }
                }
                i++;
            }
        }

        private bool CanRemove(int var_no, List<ThreeAddressCode> ir)
        {
            foreach (ThreeAddressCode i in ir)
            {
                if (i is PhiEx)
                {
                    PhiEx phi = i as PhiEx;
                    if (phi.Var_Args.Contains(var_no))
                        return false;
                }
                if (i.Operand1.logical_var == var_no)
                {
                    if((i.Operand1.type == var.var_type.ContentsOfAddress) || 
                        (i.Operand1.type == var.var_type.ContentsOfAddressPlusConstant) ||
                        (i.GetOpType() == ThreeAddressCode.OpType.OtherOp))
                            return false;
                }
                if (i.Operand2.logical_var == var_no)
                {
                    if ((i.Operand2.type == var.var_type.ContentsOfAddress) ||
                        (i.Operand2.type == var.var_type.ContentsOfAddressPlusConstant) ||
                        (i.GetOpType() == ThreeAddressCode.OpType.OtherOp))
                        return false;
                }
                if (i.Result.logical_var == var_no)
                {
                    if ((i.Result.type == var.var_type.ContentsOfAddress) ||
                        (i.Result.type == var.var_type.ContentsOfAddressPlusConstant) ||
                        (i.GetOpType() == ThreeAddressCode.OpType.OtherOp))
                        return false;
                }
            }
            return true;
        }

        private bool EvaluateConstant(ThreeAddressCode threeAddressCode)
        {
            switch (threeAddressCode.Operator)
            {
                case ThreeAddressCode.Op.conv_i_i1sx:
                case ThreeAddressCode.Op.conv_i4_i1sx:
                case ThreeAddressCode.Op.conv_i8_i1sx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayI1(ToByteArraySignExtend(threeAddressCode.Operand1.constant_val, 1));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i4;
                    return true;
                case ThreeAddressCode.Op.conv_i_i2sx:
                case ThreeAddressCode.Op.conv_i4_i2sx:
                case ThreeAddressCode.Op.conv_i8_i2sx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayI2(ToByteArraySignExtend(threeAddressCode.Operand1.constant_val, 2));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i4;
                    return true;
                case ThreeAddressCode.Op.conv_i_i4sx:
                case ThreeAddressCode.Op.conv_i8_i4sx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayI4(ToByteArraySignExtend(threeAddressCode.Operand1.constant_val, 4));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i4;
                    return true;
                case ThreeAddressCode.Op.conv_i_i8sx:
                case ThreeAddressCode.Op.conv_i4_i8sx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayI8(ToByteArraySignExtend(threeAddressCode.Operand1.constant_val, 8));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i8;
                    return true;
                case ThreeAddressCode.Op.conv_i_isx:
                case ThreeAddressCode.Op.conv_i4_isx:
                case ThreeAddressCode.Op.conv_i8_isx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayI(ToByteArraySignExtend(threeAddressCode.Operand1.constant_val, GetSizeOfIntPtr()));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i;
                    return true;
                case ThreeAddressCode.Op.conv_i_u1zx:
                case ThreeAddressCode.Op.conv_i4_u1zx:
                case ThreeAddressCode.Op.conv_i8_u1zx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayU1(ToByteArrayZeroExtend(threeAddressCode.Operand1.constant_val, 1));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i4;
                    return true;
                case ThreeAddressCode.Op.conv_i_u2zx:
                case ThreeAddressCode.Op.conv_i4_u2zx:
                case ThreeAddressCode.Op.conv_i8_u2zx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayU2(ToByteArrayZeroExtend(threeAddressCode.Operand1.constant_val, 2));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i4;
                    return true;
                case ThreeAddressCode.Op.conv_i_u4zx:
                case ThreeAddressCode.Op.conv_i8_u4zx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayU4(ToByteArrayZeroExtend(threeAddressCode.Operand1.constant_val, 4));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i4;
                    return true;
                case ThreeAddressCode.Op.conv_i_u8zx:
                case ThreeAddressCode.Op.conv_i4_u8zx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayU8(ToByteArrayZeroExtend(threeAddressCode.Operand1.constant_val, 8));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i8;
                    return true;
                case ThreeAddressCode.Op.conv_i_uzx:
                case ThreeAddressCode.Op.conv_i4_uzx:
                case ThreeAddressCode.Op.conv_i8_uzx:
                    threeAddressCode.Operand1.constant_val = FromByteArrayU(ToByteArrayZeroExtend(threeAddressCode.Operand1.constant_val, GetSizeOfUIntPtr()));
                    threeAddressCode.Operator = ThreeAddressCode.Op.assign_i;
                    return true;
            }
            return false;
        }

        protected virtual bool ArchSpecificEvaluateConstant(ThreeAddressCode threeAddressCode)
        { return false; }

        private void CleanseOutput(List<ThreeAddressCode> ir, List<int> global_vars)
        {
            // Remove unnecessary jumps and phi functions
            int i = 0;
            while (i < ir.Count)
            {
                if (ir[i] is BrEx)
                {
                    if ((i + 1) < ir.Count)
                    {
                        if (ir[i + 1].Operator == ThreeAddressCode.Op.label)
                        {
                            if (((LabelEx)ir[i + 1]).Block_id == ((BrEx)ir[i]).Block_Target) {
                                ir.RemoveAt(i);
                                continue;
                            }
                        }
                    }
                }

                i++;
            }
        }

        private void PopulatePhi(List<cfg_node> nodes)
        {
            foreach (cfg_node cfg_node in nodes)
                PopulatePhi(cfg_node);
        }

        private void PopulatePhi(cfg_node cfg_node)
        {
            if ((cfg_node._tacs_phi.Count > 0) && (cfg_node._tacs_phi[0].GetOpType() == ThreeAddressCode.OpType.PhiOp))
            {
                foreach(ThreeAddressCode tac in cfg_node._tacs_phi)
                {
                    if (!(tac is PhiEx))
                        continue;
                    PhiEx phi = tac as PhiEx;

                    foreach (cfg_node pred in cfg_node.ipred)
                    {
                        int vnum;
                        switch (phi.VarLoc)
                        {
                            case PhiEx.VariableLoc.LArg:
                                vnum = pred.la_after[phi.VarNum].contains_variable;
                                break;
                            case PhiEx.VariableLoc.LVar:
                                vnum = pred.lv_after[phi.VarNum].contains_variable;
                                break;
                            case PhiEx.VariableLoc.PStack:
                                vnum = pred.pstack_after[phi.VarNum].contains_variable;
                                break;
                            default:
                                throw new NotSupportedException();
                        }
                        if ((vnum != 0) & (!phi.Var_Args.Contains(vnum)))
                            phi.Var_Args.Add(vnum);
                    }
                }
            }
        }

        /*Signature.Param GetArgument(Metadata m, Metadata.MethodDefRow meth, int p)
        {
            Signature.Param param = null;
            Signature.Method sig = Signature.ParseMethodDefSig(m, meth.Signature);
            if ((sig.HasThis == true) && (sig.ExplicitThis == false))
            {
                if (p == 0)
                {
                    param = new Signature.Param();
                    param.ByRef = false;
                    param.CustomMods = new List<Signature.CustomMod>();
                    param.TypedByRef = false;
                    param.Type = new Signature.ComplexType { Pinned = false, Type = new Metadata.TableIndex(m, Metadata.GetOwningType(m, meth)).ToToken(m) };
                }
                else
                    param = sig.Params[p - 1];
            }
            else
                param = sig.Params[p];
            return param;
        } */

        protected abstract IRegisterAllocator GetRegisterAllocator();
        protected abstract void ArchSpecific(List<ThreeAddressCode> ir, List<cfg_node> nodes, AssemblerState state);
        protected abstract hloc_constraint GetConstraintFromSemantic(var_semantic vs);
        protected abstract void arch_init_opcodes();

        //protected abstract hardware_location GetLocalArgLocation(var v);
        //protected abstract hardware_location GetLocalVarLocation(var v);

        protected abstract List<byte> SaveLocation(hardware_location loc);
        protected abstract List<byte> RestoreLocation(hardware_location loc);
        public abstract List<byte> SwapLocation(hardware_location a, hardware_location b);
        protected abstract bool CanCoerce(hardware_location dest, hardware_location src, List<OutputBlock> coercion, IRegisterAllocator reg_alloc);

        protected abstract uint DataToDataRelocType();
        public abstract byte[] IntPtrByteArray(object v);
        protected abstract object ConvertToI(object v);
        protected abstract object ConvertToU(object v);

        public abstract int GetSizeOfUncondBr();

        protected virtual int GetDataId(Token t)
        {
            // TODO
            //throw new NotImplementedException();
            return 0;
        }
        protected virtual int GetDataOffset(Token t)
        {
            // TODO
            //throw new NotImplementedException();
            return 0;
        }
        public virtual int GetSizeOf(Signature.Param p)
        {
            // TODO - override in various code generators - the size of p (return sizeof native int for a
            //  reference type)
            return 0;
        }
        public virtual int GetSizeOfIntPtr()
        {
            return GetSizeOf(new Signature.Param(BaseType_Type.I));
        }
        public virtual int GetSizeOfUIntPtr()
        {
            return GetSizeOf(new Signature.Param(BaseType_Type.U));
        }
        public virtual int GetSizeOfPointer()
        {
            return GetSizeOf(new Signature.Param(BaseType_Type.I));
        }
        protected virtual int GetSizeOfType(Signature.Param p)
        {
            Metadata.TypeDefRow tdr = Metadata.GetTypeDef(p.Type, this);
            return Layout.GetClassSize(new TypeToCompile { type = tdr, tsig = p }, this);
        }
        protected virtual int GetArrayStart()
        {
            // TODO - override in various code generators - the offset from the start of an array object to
            //  the first element
            return 0;
        }
        protected virtual int GetFieldOffset(Token t)
        {
            // TODO - override in various code generators - the offset from the the start of an object to a
            //  particular field;
            return 0;
        }
        protected virtual int GetArrayLengthStart()
        {
            // TODO - override in various code generators - the offset from the start of an array object to
            //  the element describing its length (size native int)
            return 0;
        }

        public string GetName()
        {
            if (this.GetType().Name.EndsWith("_Assembler"))
                return this.GetType().Name.Substring(0, this.GetType().Name.Length - 10);
            else
                return this.GetType().Name;
        }

        /*public static String MethodAliasCtorSignature = "Cli::MethodAlias::.ctor::2001010E::M";
        public static String TypeAliasCtorSignature = "Cli::TypeAlias::.ctor::2001010E::M";
        public static String BuiltinFunctionCtorSignature = "Cli::BuiltinFunction::.ctor::2001010E::M";
        public static String NoStoreFramePointerCtorSignature = "Cli::NoStoreFramePointer::.ctor::200001::M";
        public static String SetRegisterOnEnterCtorSignature = "Cli::SetRegisterOnEnter::.ctor::2002010419::M";
        public static String SetStackPointerOnEnterCtorSignature = "Cli::SetStackPointerOnEnter::.ctor::2001010E::M";
        public static String DefineGlobalCtorSignature = "Cli::DefineGlobal::.ctor::2001010E::M";

        public static String SystemTypeSignature = "mscorlib::System::Type::T";
        public static String SystemFieldInfoSignature = "mscorlib::System.Reflection::FieldInfo::T";
        public static String SystemArraySignature = "mscorlib::System::Array::T";
        public static String SystemPropertyInfoSignature = "mscorlib::System.Reflection::PropertyInfo::T";
        public static String SystemEventInfoSignature = "mscorlib::System.Reflection::EventInfo::T";
        public static String SystemMethodInfoSignature = "mscorlib::System.Reflection::MethodInfo::T";
        public static String SystemParameterInfoSignature = "mscorlib::System.Reflection::ParameterInfo::T";

        public static String CliMemoryAllocatorAllocImplementation = "tscorlib::Cli::MemoryAllocator::Alloc::00010B0B::MI";
        public static String CliExceptionsThrowObjectImplementation = "tscorlib::Cli::Exceptions::Throw::0001011C::MI";
        public static String CliExceptionsThrowIntegerImplementation = "tscorlib::Cli::Exceptions::Throw::00010108::MI";
        public static String CliRuntimeCheckPureVirtualImplementation = "tscorlib::Cli::Runtime::PureVirtual::000001::MI";
        public static String CliRuntimeCheckDynamicCast = "tscorlib::Cli::Runtime::DynamicCast::0002181D1818::MI";
        public static String CliRuntimeGetHandleFromType = "tscorlib::Cli::Runtime::GetHandleFromType::00011C1C::MI";
        public static String CliRuntimeGetHandleFromMethod = "tscorlib::Cli::Runtime::GetHandleFromMethod::00011C1C::MI";
        public static String CliRuntimeGetHandleFromField = "tscorlib::Cli::Runtime::GetHandleFromField::00011C1C::MI";*/

        public class InvalidOpcodeException : Exception
        {
            public InvalidOpcodeException(int opcode) {
                _opcode = opcode;
            }

            public int Opcode { get { return _opcode; } }

            private int _opcode;
        }

        public class StackUnderflowException : Exception { }

        public enum CliType
        {
            int32, int64, native_int, F, O, reference, void_, vt
        }

        #region IByteOperations Members
        public abstract object FromByteArray(BaseType_Type type, IList<byte> v, int offset);
        public abstract object FromByteArray(BaseType_Type type, IList<byte> v);
        public abstract ulong FromByteArrayU8(IList<byte> v);
        public abstract ulong FromByteArrayU8(IList<byte> v, int offset);
        public abstract long FromByteArrayI8(IList<byte> v);
        public abstract long FromByteArrayI8(IList<byte> v, int offset);
        public abstract uint FromByteArrayU4(IList<byte> v);
        public abstract uint FromByteArrayU4(IList<byte> v, int offset);
        public abstract int FromByteArrayI4(IList<byte> v);
        public abstract int FromByteArrayI4(IList<byte> v, int offset);
        public abstract byte FromByteArrayU1(IList<byte> v);
        public abstract byte FromByteArrayU1(IList<byte> v, int offset);
        public abstract sbyte FromByteArrayI1(IList<byte> v);
        public abstract sbyte FromByteArrayI1(IList<byte> v, int offset);
        public abstract ushort FromByteArrayU2(IList<byte> v);
        public abstract ushort FromByteArrayU2(IList<byte> v, int offset);
        public abstract short FromByteArrayI2(IList<byte> v);
        public abstract short FromByteArrayI2(IList<byte> v, int offset);
        public abstract char FromByteArrayChar(IList<byte> v);
        public abstract char FromByteArrayChar(IList<byte> v, int offset);
        public abstract float FromByteArrayR4(IList<byte> v);
        public abstract float FromByteArrayR4(IList<byte> v, int offset);
        public abstract double FromByteArrayR8(IList<byte> v);
        public abstract double FromByteArrayR8(IList<byte> v, int offset);
        public abstract IntPtr FromByteArrayI(IList<byte> v);
        public abstract IntPtr FromByteArrayI(IList<byte> v, int offset);
        public abstract UIntPtr FromByteArrayU(IList<byte> v);
        public abstract UIntPtr FromByteArrayU(IList<byte> v, int offset);
        public abstract byte[] ToByteArray(byte v);
        public abstract byte[] ToByteArray(sbyte v);
        public abstract byte[] ToByteArray(short v);
        public abstract byte[] ToByteArray(ushort v);
        public abstract byte[] ToByteArray(int v);
        public abstract byte[] ToByteArray(uint v);
        public abstract byte[] ToByteArray(long v);
        public abstract byte[] ToByteArray(ulong v);
        public abstract byte[] ToByteArray(IntPtr v);
        public abstract byte[] ToByteArray(UIntPtr v);
        public abstract byte[] ToByteArray(ValueType v);
        public abstract byte[] ToByteArray(bool v);
        public abstract byte[] ToByteArray(float v);
        public abstract byte[] ToByteArray(double v);
        public abstract byte[] ToByteArrayZeroExtend(object v, int byte_count);
        public abstract byte[] ToByteArraySignExtend(object v, int byte_count);
        public abstract void SetByteArray(IList<byte> target, int t_offset, IList<byte> source, int s_offset, int s_size);
        public abstract void SetByteArray(IList<byte> target, int t_offset, byte v);
        public abstract void SetByteArray(IList<byte> target, int t_offset, short v);
        public abstract void SetByteArray(IList<byte> target, int t_offset, int v);
        public abstract void SetByteArray(IList<byte> target, int t_offset, int v, int v_size);
        public virtual void SetByteArray(IList<byte> target, int t_offset, IList<byte> source) { SetByteArray(target, t_offset, source, 0, source.Count); }
        #endregion

        internal abstract string GetCType(BaseType_Type baseType_Type);
        public abstract bool PassesByReference(Assembler.TypeToCompile ttc);

        internal string GetUnderlyingCType(Metadata.TypeDefRow tdr)
        {
            if (!tdr.IsEnum(this))
                throw new InvalidOperationException();
            Layout l = new Layout(new TypeToCompile { type = tdr, tsig = new Signature.Param(tdr, this) }, this);
            foreach(Layout.ImplementedInterface.ImplementedInterfaceField iif in l.Fields)
            {
                if((iif.Type == Layout.Field.FieldType.Field) && (iif.Field.field.Name == "value__"))
                    return GetCType(iif.Field.field);
            }
            throw new InvalidOperationException();
        }

        internal string GetCType(Signature.Param fsig)
        {
            if (fsig.Type is Signature.BaseType)
                return GetCType(((Signature.BaseType)fsig.Type).Type);
            else if (fsig.Type is Signature.ComplexType)
            {
                Signature.ComplexType ct = fsig.Type as Signature.ComplexType;
                Metadata.TypeDefRow tdr = Metadata.GetTypeDef(ct.Type, this);
                if (tdr.IsEnum(this))
                    return GetUnderlyingCType(tdr);
                else if (tdr.IsValueType(this))
                    return "struct " + tdr.TypeNamespace + "_" + tdr.TypeName;
                else
                    return GetCType(BaseType_Type.U);
            }
            else if (fsig.Type is Signature.ZeroBasedArray)
            {
                return GetCType(BaseType_Type.U);
            }
            else
                throw new NotSupportedException();
        }

        internal string GetCType(Metadata.FieldRow fr)
        {
            Signature.Field fsig = Signature.ParseFieldSig(fr.m, fr.Signature);
            return GetCType(fsig.AsParam(this));
        }

        public virtual byte[] ToByteArray(object v)
        {
            if (v is bool)
                return ToByteArray((bool)v);
            else if (v is byte)
                return ToByteArray((byte)v);
            else if (v is sbyte)
                return ToByteArray((sbyte)v);
            else if (v is short)
                return ToByteArray((short)v);
            else if (v is ushort)
                return ToByteArray((ushort)v);
            else if (v is int)
                return ToByteArray((int)v);
            else if (v is uint)
                return ToByteArray((uint)v);
            else if (v is long)
                return ToByteArray((long)v);
            else if (v is ulong)
                return ToByteArray((ulong)v);
            else if (v is IntPtr)
                return ToByteArray((IntPtr)v);
            else if (v is UIntPtr)
                return ToByteArray((UIntPtr)v);
            else if (v is float)
                return ToByteArray((float)v);
            else if (v is double)
                return ToByteArray((double)v);
            else if (v is ValueType)
                return ToByteArray((ValueType)v);
            else
                throw new Exception("Unable to convert that type to byte string");
        }

        public bool Implements(ThreeAddressCode.Op op)
        {
            return output_opcodes.ContainsKey(op);
        }

        public class AssemblerException : Exception
        { }

        public class TooManyIterationsException : AssemblerException
        { }

        public class TooManyCoercionsException : AssemblerException
        {
            ThreeAddressCode inst;
            public ThreeAddressCode Instruction { get { return inst; } }
            public TooManyCoercionsException(ThreeAddressCode _inst) { inst = _inst; }
        }

        protected Dictionary<BaseType_Type, Metadata.TypeDefRow> basetype_tdrs = new Dictionary<BaseType_Type, Metadata.TypeDefRow>();
        public Metadata.TypeDefRow GetBasetypeTypedef(Signature.BaseType bt)
        {
            if (basetype_tdrs.ContainsKey(bt.Type))
                return basetype_tdrs[bt.Type];

            Metadata corlib = FindAssembly(new Metadata.AssemblyRefRow { Name = "mscorlib" });

            foreach (Metadata.TypeDefRow tdr in corlib.Tables[(int)Metadata.TableId.TypeDef])
            {
                if ((tdr.TypeNamespace == "System") && (tdr.TypeName == bt.GetTypeName()))
                {
                    basetype_tdrs.Add(bt.Type, tdr);
                    return tdr;
                }
            }
            return null;
        }
    }

    class AssemblerInfo
    {
        public enum ProcessorType
        {
            x86, x86_64, PPC, IA64, ARM, Any, Other
        }

        public enum ReqSystem
        {
            Win32, WinCE, OSX, Linux, FreeBSD, OpenBSD, Unix, Any, None
        }

        public enum SystemExtension
        {
            Gui, Vista, WinXP, Win2000, WinNT4, WM6, WM5, PPC2003, CellularAccess, Linux_2_6, Linux_2_4, Linux_2_2,
            Linux_2_0, KDE, GNOME, X11, SSL
        }

        ProcessorType pt;
        ReqSystem st;
        bool pic;
        byte pad_byte;
        int default_alignment;

        public ProcessorType Processor { get { return pt; } }
        public ReqSystem System { get { return st; } }
        public bool PIC { get { return pic; } }
        public byte PadByte { get { return pad_byte; } }
        public int DefaultAlignment { get { return default_alignment; } }

        public AssemblerInfo(ProcessorType ptype, bool make_pic, byte padbyte, int defalign)
        { pt = ptype; pic = make_pic; st = ReqSystem.None; pad_byte = padbyte; default_alignment = defalign; }
    }
}
